package com.step.graphic;

import java.nio.IntBuffer;
import java.util.ArrayList;

import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLUtils;

import com.step.Step;
import com.step.io.FileHandle;
import com.step.utils.BufferUtils;
import com.step.utils.MathUtils;

public class Texture {
	public enum TextureFilter {
		Nearest(GL10.GL_NEAREST), 
		Linear(GL10.GL_LINEAR), 
		MipMap(GL10.GL_LINEAR_MIPMAP_LINEAR), 
		MipMapNearestNearest(GL10.GL_NEAREST_MIPMAP_NEAREST),
		MipMapLinearNearest(GL10.GL_LINEAR_MIPMAP_NEAREST), 
		MipMapNearestLinear(GL10.GL_NEAREST_MIPMAP_LINEAR), 
		MipMapLinearLinear(GL10.GL_LINEAR_MIPMAP_LINEAR);

		final int glEnum;

		TextureFilter (int glEnum) {
			this.glEnum = glEnum;
		}

		public boolean isMipMap () {
			return glEnum != GL10.GL_NEAREST && glEnum != GL10.GL_LINEAR;
		}

		public int getGLEnum () {
			return glEnum;
		}
	}
	
	public enum TextureWrap {
		ClampToEdge(GL10.GL_CLAMP_TO_EDGE), Repeat(GL10.GL_REPEAT);

		final int glEnum;

		TextureWrap (int glEnum) {
			this.glEnum = glEnum;
		}

		public int getGLEnum () {
			return glEnum;
		}
	}
	
	private static boolean enforcePotSize = true;
	private static boolean useHWMipMap = true;
	private static final IntBuffer buffer = BufferUtils.newIntBuffer(1);
	public int width;
	public int height;
	int glTextureId;
	FileHandle file;
	
	public Texture (String internalPath) {
		this(Step.files.internal(internalPath), false);
	}

	
	public Texture (FileHandle file) {
		this(file, false);
	}

	public Texture (FileHandle file, boolean useMipMaps) {
		this.file = file;
		useHWMipMap = useMipMaps;
		load();
		TextureManager.addManagedTexture(Step.context, this);
	}
	
	public Texture(Pixmap pixmap, boolean useMipMaps){
		useHWMipMap = useMipMaps;
		load(pixmap);
		TextureManager.addManagedTexture(Step.context, this);
	}
	
	public int load(){
		return load(new Pixmap(file));
	}
	
	int load(Pixmap pixmap){
		GL10 gl = Step.gl;
		glTextureId = createTextureId(gl);
		
		width = pixmap.getWidth();
		height = pixmap.getHeight();
		
		if(enforcePotSize){
			ensurePot(pixmap);
		}
		
		uploadImage(pixmap, gl);
		
		gl.glBindTexture(GL10.GL_TEXTURE_2D, 0);
		
		return glTextureId;
	}
	
	void reload () {
		glTextureId = createTextureId(Step.gl);
		load();
	}
	
	// 确保图像大小为2的ｎ次方
	private void ensurePot(Pixmap pixmap){
		int width = pixmap.getWidth();
		int height = pixmap.getHeight();
		int potWidth = MathUtils.nextPowerOfTwo(width);
		int potHeight = MathUtils.nextPowerOfTwo(height);
		
		if(width != potWidth || height != potHeight){			
			Pixmap newPixmap = pixmap.resize(potWidth, potHeight, false);
			pixmap.dispose();
			pixmap = newPixmap;
		}
		
		Step.log("Texture-原图大小："+width+" X "+ height);
		Step.log("Texture-现图大小："+potWidth+" X "+ potHeight);
	}

	private void uploadImage(Pixmap pixmap, GL10 gl){
		if(enforcePotSize && (!MathUtils.isPowerOfTwo(pixmap.getWidth()) || !MathUtils.isPowerOfTwo(pixmap.getHeight()))){
			throw new RuntimeException("Texture-要求图像大小为2的ｎ次方");
		}
		
		gl.glBindTexture(GL10.GL_TEXTURE_2D, glTextureId);
		gl.glPixelStorei(GL10.GL_UNPACK_ALIGNMENT, 1);
		
		if(useHWMipMap){
			createMipmaps(pixmap, gl);
		}
		else{
			GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, pixmap.bitmap, 0);
			gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
			gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
		}
		
		pixmap.dispose();
	}

	private void createMipmaps(Pixmap pixmap, GL10 gl){		
		// 设置过滤器
		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR_MIPMAP_NEAREST);
		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
		
		int level = 0;
		int newWidth = pixmap.getWidth();
		int newHeight = pixmap.getHeight();
		
		while(newWidth >= 2){
			GLUtils.texImage2D(GL10.GL_TEXTURE_2D, level, pixmap.bitmap, 0);
			newWidth /= 2;
			newHeight /= 2;
			Pixmap newPixmap = pixmap.resize(newWidth, newHeight, true);
			pixmap.dispose();
			pixmap = newPixmap;
			level++;
		}
	}
	
	// 创建纹理句柄
	private int createTextureId(GL10 gl){
		buffer.position(0);
		buffer.limit(buffer.capacity());
		gl.glGenTextures(1, buffer);
		return buffer.get(0);
	}
	
	public void bind(){
		Step.gl.glBindTexture(GL10.GL_TEXTURE_2D, glTextureId);
	}
	
	public void bind(int unit){
		Step.gl.glActiveTexture(GL10.GL_TEXTURE0+unit);
		Step.gl.glBindTexture(GL10.GL_TEXTURE_2D, glTextureId);
	}
	
	public void dispose(){
		if(glTextureId == 0) return;
		buffer.put(0, glTextureId);
		Step.gl.glDeleteTextures(1, buffer);
		
		ArrayList<Texture> textureList = TextureManager.managedTextures.get(Step.context);
		
		if(textureList != null){
			textureList.remove(this);
		}
		
		glTextureId = 0;
	}
	
	TextureFilter minFilter = TextureFilter.Nearest;
	TextureFilter magFilter = TextureFilter.Nearest;
	TextureWrap uWrap = TextureWrap.ClampToEdge;
	TextureWrap vWrap = TextureWrap.ClampToEdge;
	
	public void setWrap (TextureWrap u, TextureWrap v) {
		this.uWrap = u;
		this.vWrap = v;
		bind();
		Step.gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, u.getGLEnum());
		Step.gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, v.getGLEnum());
	}

	public void setFilter (TextureFilter minFilter, TextureFilter magFilter) {
		this.minFilter = minFilter;
		this.magFilter = magFilter;
		bind();
		Step.gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, minFilter.getGLEnum());
		Step.gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, magFilter.getGLEnum());
	}

	public static void setEnforcePotSize(boolean enforcePotSize) {
		Texture.enforcePotSize = enforcePotSize;
	}

	public int getWidth() {
		return width;
	}

	public int getHeight() {
		return height;
	}

	public int getGlTextureId() {
		return glTextureId;
	}

	public TextureFilter getMinFilter() {
		return minFilter;
	}

	public TextureFilter getMagFilter() {
		return magFilter;
	}

	public TextureWrap getuWrap() {
		return uWrap;
	}

	public TextureWrap getvWrap() {
		return vWrap;
	}
	
	private TextureRegion region;
	public TextureRegion getRegion(){
		if(region == null) region = new TextureRegion(this);
		return region;
	}
}
